package org.datacoder.benchmark

import org.datacoder.geocoding.*
import org.datacoder.geocoding.datastructures.*
import org.datacoder.geocoding.datastructures.geoshapes.*
import org.datacoder.geocoding.datastructures.intervals.*
import org.datacoder.geocoding.datastructures.points.*
import org.datacoder.geocoding.datastructures.rectangles.*
import org.datacoder.geocoding.jurisdiction.*
import org.datacoder.util.*

/**
 * A class for performing benchmarking.  TestSuite.run() performs a
 * comprehensive set of benchmarks on each of the 17 data structures
 * of Datacoder.  It stores the results in an XML file.
 */
class TestSuite {

    // The number of jurisdictions OF EACH TYPE to test on each data structure.
    private static final int NUM_TEST_JURISDICTIONS = 5
    // The number of times to query each data structure with each jurisdiction.
    private static final int NUM_REPEAT_QUERIES = 5000
    // The number of times to query each data structure with each jurisdiction
    // as a warmup (to get the JVM to optimize code paths, etc.).  Warmup
    // queries do not play a role in the results.
    private static final int NUM_WARMUP_QUERIES = 5000
    // If true, prints a substantial amount of additional information during
    // testing.  Has no effect on what goes into the XML file.
    private static final boolean VERBOSE = false
    // The five intersecting-intervals finders can perform two roles in the
    // EdelsbrunnerMaurerStructure -- they can find horizontal intervals
    // intersecting a vertical interval, or they can find vertical intervals
    // intersecting a horizontal interval.  We can skip testing the finders
    // in the latter role to speed things up.
    private static final boolean INCLUDE_INTERSECTING_VT_INTERVALS = false

    private static final Collection<Jurisdiction> jurisdictions = Jurisdiction.findAll()
    private static final Map<Class, List<Jurisdiction>> testJurisdictions = new HashMap<Class, List<Jurisdiction>>()
    private static final List<Class> ihiFinders = []
    private static final List<Class> iviFinders = []
    private static final List<Class> pwrFinders = []
    private static final List<Class> rcpFinders = []
    private static final List<Class> jurisClasses = []

    /*
     * The main method of TestSuite.
     */
    public static void run(){
        println "***Initializing TestSuite***"
        initializeFindersLists()
        initializeJurisdictionsList()

        print "Gathering jurisdictions..."
        Collection<Jurisdiction> jurisdictions = Jurisdiction.findAll()
        for(int i = 0; i < jurisClasses.size(); i++){
            Class currentClass = jurisClasses.get(i)
            int numJurisdictions = currentClass.findAll().size()
            Iterator iter = currentClass.findAll().iterator()
            List<Jurisdiction> currentTestJurisdictions = new ArrayList<Jurisdiction>()
            for(int j = 0; (j < NUM_TEST_JURISDICTIONS) && (j < numJurisdictions); j++){
                currentTestJurisdictions.add(iter.next())
            }
            testJurisdictions.put(currentClass, currentTestJurisdictions)
        }
        println "done."

        String filename = "${Jurisdiction.findAll().size()}"
        ResultHolder resultHolder = new ResultHolder(filename)

        println "Benchmarking IntersectingIntervalFinders (Hz)..."
        performBenchmarks(IntersectingIntervalsFinder, GeoShape.LON, ihiFinders, jurisdictions, resultHolder.ihiResults)
        println "done."

        if(INCLUDE_INTERSECTING_VT_INTERVALS){
            println "Benchmarking IntersectingIntervalFinders (Vt)..."
            performBenchmarks(IntersectingIntervalsFinder, GeoShape.LAT, iviFinders, jurisdictions, resultHolder.iviResults)
            println "done."
        }

        println "Benchmarking PointsWithinRectangleFinders..."
        performBenchmarks(PointsWithinRectangleFinder, GeoShape.NONE, pwrFinders, jurisdictions, resultHolder.pwrResults)
        println "done."

        println "Benchmarking RectanglesContainingPointFinders..."
        performBenchmarks(RectanglesContainingPointFinder, GeoShape.NONE, rcpFinders, jurisdictions, resultHolder.rcpResults)
        println "done."

        resultHolder.numRuns++

        println ""
        println ""
        println ""

        println "---RESULTS---"
        resultHolder.printResults(resultHolder.numRuns - 1, VERBOSE, INCLUDE_INTERSECTING_VT_INTERVALS)
        resultHolder.writeXMLFile(filename)
        println "***TestSuite Complete***"
    }

    /*
     * Performs the benchmarks on the set of finders of one of the four types.
     */
    private static void performBenchmarks(
        Class finderTypeClass,
        int type,
        List<Class> finders,
        Collection<Jurisdiction> jurisdictions,
        Map<Class, FinderStatHolder> theResults)
    {
        // By Finder
        for(clazz in finders){
            FinderStatHolder fsHolder = theResults.get(clazz)

            // Startup
            print "\t${clazz.getSimpleName()}: Startup "
            long startUpStartTime = System.nanoTime()
            EdelsbrunnerMaurerTaskStructure finder
            if(finderTypeClass == IntersectingIntervalsFinder){
                finder = (EdelsbrunnerMaurerTaskStructure)clazz.getConstructors()[0].newInstance(jurisdictions, type)
            }else{
                finder = (EdelsbrunnerMaurerTaskStructure)clazz.getConstructors()[0].newInstance(jurisdictions)
            }
            long startUpEndTime = System.nanoTime()
            double startUpTime = (1.0*(startUpEndTime - startUpStartTime))/Math.pow(10, 9)
            System.out.printf("%05.5f seconds; Query ", startUpTime)
            fsHolder.startUpTimes.add(startUpTime)

            // By Juris
            boolean failure = false
            for(int j = 0; j < jurisClasses.size(); j++){
                Class currentClass = jurisClasses.get(j)
                List<Jurisdiction> currentTestJurisdictions = testJurisdictions.get(currentClass)
                double averageTime
                try{

                    // Calculate Time
                    List<Double> currentResults = new ArrayList<Double>()
                    if(finderTypeClass == IntersectingIntervalsFinder){
                        for(int k = 0; k < currentTestJurisdictions.size(); k++){
                            Jurisdiction juris = currentTestJurisdictions.get(k)
                            GeoInterval gi = juris.toGeoInterval(GeoShape.getOtherType(type))
                            for(int l = 0; l < NUM_WARMUP_QUERIES; l++){
                                finder.findIntersectingIntervals(gi)
                            }
                            long startTime = System.nanoTime()
                            for(int l = 0; l < NUM_REPEAT_QUERIES; l++){
                                finder.findIntersectingIntervals(gi)
                            }
                            long endTime = System.nanoTime()
                            currentResults.add((1.0*(endTime - startTime)/NUM_REPEAT_QUERIES)/Math.pow(10, 9))
                        }
                        averageTime = getAverage(currentResults)
                    }else if(finderTypeClass == PointsWithinRectangleFinder){
                        for(int k = 0; k < currentTestJurisdictions.size(); k++){
                            Jurisdiction juris = currentTestJurisdictions.get(k)
                            GeoRectangle gr = juris.toGeoRectangle()
                            for(int l = 0; l < NUM_WARMUP_QUERIES; l++){
                                finder.findPointsWithinRectangle(gr)
                            }
                            long startTime = System.nanoTime()
                            for(int l = 0; l < NUM_REPEAT_QUERIES; l++){
                                finder.findPointsWithinRectangle(gr)
                            }
                            long endTime = System.nanoTime()
                            currentResults.add((1.0*(endTime - startTime)/NUM_REPEAT_QUERIES)/Math.pow(10, 9))
                        }
                        averageTime = getAverage(currentResults)
                    }else{
                        for(int k = 0; k < currentTestJurisdictions.size(); k++){
                            Jurisdiction juris = currentTestJurisdictions.get(k)
                            GeoPoint gp = juris.toGeoPoint()
                            for(int l = 0; l < NUM_WARMUP_QUERIES; l++){
                                finder.findRectanglesContainingPoint(gp)
                            }
                            long startTime = System.nanoTime()
                            for(int l = 0; l < NUM_REPEAT_QUERIES; l++){
                                finder.findRectanglesContainingPoint(gp)
                            }
                            long endTime = System.nanoTime()
                            currentResults.add((1.0*(endTime - startTime)/NUM_REPEAT_QUERIES)/Math.pow(10, 9))
                        }
                        averageTime = getAverage(currentResults)
                    }
                    fsHolder.byJurisTimes.get(currentClass).add(averageTime)
                }catch(Exception e){
                    println ""
                    e.printStackTrace(System.out)
                    println ""
                    averageTime = -1
                    failure = true
                }
            }

            double overallAverageTime
            if(failure){
                overallAverageTime = -1
            }else{
                List<Double> overallTimes = new ArrayList<Double>()
                for(currentClass in jurisClasses){
                    double averageTime = fsHolder.byJurisTimes.get(currentClass).getLast()
                    int numTestJurisdictions = testJurisdictions.get(currentClass).size()
                    for(int j = 0; j < numTestJurisdictions; j++){
                        overallTimes.add(averageTime)
                    }
                }
                overallAverageTime = getAverage(overallTimes)
            }
            fsHolder.averageQueryTimes.add(overallAverageTime)
            fsHolder.numRuns++
            System.out.printf("%05.5f seconds\n", overallAverageTime)
        }
    }

    private static void initializeFindersLists(){
        GeneralUtils.initializeClassList(GeneralUtils.INTERVALS_PACKAGE, IntersectingIntervalsFinder, ihiFinders)
        GeneralUtils.initializeClassList(GeneralUtils.INTERVALS_PACKAGE, IntersectingIntervalsFinder, iviFinders)
        GeneralUtils.initializeClassList(GeneralUtils.POINTS_PACKAGE, PointsWithinRectangleFinder, pwrFinders)
        GeneralUtils.initializeClassList(GeneralUtils.RECTANGLES_PACKAGE, RectanglesContainingPointFinder, rcpFinders)
    }
    private static void initializeJurisdictionsList(){
        GeneralUtils.initializeClassList(GeneralUtils.JURISDICTION_PACKAGE, Jurisdiction, jurisClasses)
    }
    private static double getAverage(List<Double> times){
        double total = 0
        for(time in times){
            total += time
        }
        return total/times.size()
    }
}


